Python 您所在的位置:网站首页 python 线程互斥 Python

Python

2023-11-01 16:10| 来源: 网络整理| 查看: 265

Python 中的多线程 什么是线程

​ 一个进程中包括多个线程,线程是 CPU 调度和分派的基本单位,是进程中执行运算的最小单位,真正在 CPU 上运行的是线程,可以与同一个进程中的其他线程共享进程的全部资源

Python 中实现多线程

​ Python 中有两种方式床架多线程,一种是调用底层的 thread模块(Python3 中已弃用),另一种是使用threading模块,下面我说的也是使用这个模块实现多线程的方法

​ 从形式上将,多线程的实现和多进程的实现十分类似,threading模块提供了Thread类来创建线程,同Process类一样,我们可以通过直接调用或创建子类来继承这两种方式来创建线程

使用 Thread 实现多线程

​ 直接调用threading,模块的Thread类来创建线程十分简单,使用threading.Thread([target], [(item1, item2, ...)])方法,target为目标函数名称(如果有目标函数的话),后边为参数元组,用来传递函数参数

​ 线程创建好后,调用Thread.start()方法,就可以运行线程,如果没有目标函数,start()会自动执行Thread类中的run()方法,示例如下:

import threading import time def saySorry(): time.sleep(5) print('I am sorry') if __name__ == '__main__': for i in range(5): print('create %i Thread' % i) t = threading.Thread(target=saySorry) t.start()

​ 运行结果:

create 0 Thread create 1 Thread create 2 Thread create 3 Thread create 4 Thread I am sorry I am sorry I am sorry I am sorry I am sorry

​ 上边的程序也证明了,程序在创建了子线程后,不会等待线程执行完毕,而是会继续向下执行,而当主程序全部执行完毕后,却会等待所有子线程执行完毕再结束,这点和进程有些区别

继承 Thread 实现多线程

​ 另外一种方式是通过创建继承Thread类的子类来实现多线程,这样做的好处就是可以将线程要运行的代码全部放入run函数中,用起来更方便,示例如下:

import threading import time class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = 'I am ' + self.name + ' @ ' + str(i) print(msg) if __name__ == '__main__': t = MyThread() t.start()

​ 运行结果:

I am Thread-1 @ 0 I am Thread-1 @ 1 I am Thread-1 @ 2 Python 多线程中全局变量和非全局变量的使用问题

​ 开始就说过,线程可以和同一个进程中的其他线程共享进程的全部资源,那么在多线程程序中,各线程对全局变量和非全局变量的使用到底是怎样的呢

非全局变量

​ 非全局变量在多线程中是不会被共享的,这就像是假设有一个存在局部变量a的函数,当程序调用两次这个函数时,每次调用所产生的局部变量a都是一个新的变量,不会受另一个函数执行的干扰,示例如下:

from threading import Thread import threading def test1(): str = threading.current_thread().name g_num = 100 if str == 'Thread-1': print(str) g_num += 1 else: print(str) g_num -= 1 print('-----test1----- g_num = %d' % g_num) p1 = Thread(target=test1) p1.start() p2 = Thread(target=test1) p2.start()

​ 运行结果:

Thread-1 -----test1----- g_num = 101 Thread-2 -----test1----- g_num = 99

​ 可以看到,局部变量g_num并不会因为另一个线程中的同名函数而收到影响

全局变量

​ 在多线程中,全局变量是可以在各线程间共享的,这也就是说,线程间通信不需要通过管道、内存映射等方法,只需要使用一个全局变量(同一个进程中的共享资源)便可以,示例如下:

from threading import Thread g_flag = 0 g_num = 0 def test1(): global g_num for i in range(1000000): g_num += 1 print("---test1--- g_num = %d" % g_num) def test2(): global g_num for i in range(1000000): g_num += 1 print("---test2--- g_num = %d " % g_num) p1 = Thread(target=test1) p1.start() p2 = Thread(target=test2) p2.start() print("---g_num= %d ---" % g_num)

​ 运行结果:

---g_num= 372639 --- ---test1--- g_num = 1456098 ---test2--- g_num = 1596586

​ 到这里,我们可以发现一个问题,正常来讲,g_num最后的值不应给是2000000吗,为什么不是呢?这就是多线程使用全局变量时有可能出现的 bug,请继续阅读!

Python 多线程中如何防止使用全局变量出现 bug(轮询和互斥锁)

​ 通过刚才在多线程中使用全局变量我们发现,当代码逻辑稍微复杂一些时,在两个线程中同时使用一个全局变量会出现问题,是什么导致了这个问题呢?

​ 从代码中我们可以发现g_num += 1这句代码,实际上是g_num + 1和将其结果赋给g_num两步,正是因为这连续的两次对全局变量的操作造成了这个问题

​ 当一个线程执行到g_num + 1这步后,cpu 有可能会转头去处理另一个线程,另一个线程也运行了g_num + 1,当 cpu 再回头执行第一个线程时,g_num 已经不止被运算过一次了

​ 那么怎么避免这样的情况发生呢,只能是如果存在对全局变量变量值的修改时,我们要优先运行一个线程,当它结束修改后,再允许另一个线程去访问这个局部变量,下面提供两种方式,轮询和互斥锁

轮询

​ 顾名思义,轮询的意思就是反复询问,抽象起来理解就是,我们可以设置另一个用来作为目标值的全局变量,两个线程执行的条件根据目标值的不同而不同,当目标值满足一个线程执行时,其他线程就会一直处在一个堵塞的过程,它会一直询问目标值是否符合自己,当上一个线程结束时,这个线程会将目标值修改,这样下一个符合目标值的线程就会运行,示例如下:

from threading import Thread g_flag = 0 g_num = 0 def test1(): global g_num global g_flag if g_flag == 0: for i in range(1000000): g_num += 1 g_flag = 1 print("---test1--- g_num = %d" % g_num) def test2(): global g_num global g_flag while True: if g_flag != 0: for i in range(1000000): g_num += 1 break print("---test2--- g_num = %d " % g_num) p1 = Thread(target=test1) p1.start() p2 = Thread(target=test2) p2.start() print("---g_num= %d ---" % g_num)

​ 运行结果:

---g_num= 181893 --- ---test1--- g_num = 1000000 ---test2--- g_num = 2000000

​ 如结果所示,线程之间使用全局变量的 bug 已经解决,但是轮询的方法十分消耗资源,堵塞的线程其实一直都处在一个死循环的状态占用系统资源

互斥锁

​ 相比而言,互斥锁就是一种比较优化的方法,互斥锁会使用threading模块的Lock类

​ 互斥锁的思想是,当一个线程运行时,它会给它需要的这部分资源上锁,这样同样使用这把锁的其他线程全部都会被堵塞,但被互斥锁所堵塞的线程不会占用系统资源,它们会处在睡眠状态,当运行的线程用完被锁的这部分资源后,它会解锁,这时其他线程就会被唤醒来抢占 cpu 资源,得到资源的线程会再次上锁,达到多线程下全局变量的访问安全,示例如下:

from threading import Thread from threading import Lock g_num = 0 mutex = Lock() def test1(): global g_num mutex.acquire() for i in range(1000000): g_num += 1 mutex.release() print("---test1--- g_num = %d" % g_num) def test2(): global g_num mutex.acquire() for i in range(1000000): g_num += 1 mutex.release() print("---test2--- g_num = %d " % g_num) p1 = Thread(target=test1) p1.start() p2 = Thread(target=test2) p2.start() print("---g_num= %d ---" % g_num)

​ 运行结果:

---g_num= 200009 --- ---test1--- g_num = 1000000 ---test2--- g_num = 2000000

​ 互斥锁的方法说明:

# 创建锁 mutex = threading.Lock() # 上锁,blocking 为 True 表示堵塞 mutex.acquire([blocking]) # 解锁,只要开了锁,那么接下来会让所有因为这个锁而被阻塞的线程抢着上锁 mutex.release()

​ 使用互斥锁时要注意,为了提高运算效率,上锁的资源越少,运算的效率越高

​ 另外,线程等待解锁的方式不是通过轮询,二十通过通知,线程会睡眠,等待唤醒的通知,所以互斥锁较轮询来讲更为优化



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有